import pyexcel as pe

from ._compact import OrderedDict
from .nose_tools import eq_, raises


class Attributable:
    def __init__(self, adict):
        self.mydict = adict

    def __getattr__(self, field):
        return self.mydict[field]


class Objects:
    def __init__(self):
        self.objs = None

    def bulk_create(self, objs, batch_size):
        self.objs = objs
        self.batch_size = batch_size

    def all(self):
        return [Attributable(o) for o in self.objs]


class Field:
    def __init__(self, name):
        self.attname = name


class Meta:
    instance = 1

    def __init__(self):
        self.model_name = "Sheet%d" % Meta.instance
        self.concrete_fields = []
        Meta.instance = Meta.instance + 1

    def update(self, data):
        for f in data:
            self.concrete_fields.append(Field(f))


class FakeDjangoModel:
    def __init__(self, model_name=None):
        self.objects = Objects()
        self._meta = Meta()
        if model_name:
            self._meta.model_name = model_name

    def __call__(self, **keywords):
        return keywords

    def save(self):
        pass


def get_data():
    return [["X", 1, 4], ["Y", 2, 5], ["Z", 3, 6]]


RESULT_ = [{"Y": 2, "X": 1, "Z": 3}, {"Y": 5, "X": 4, "Z": 6}]


def test_model_save_to_django_model_1():
    model = FakeDjangoModel()
    pe.save_as(
        array=get_data(),
        name_columns_by_row=0,
        dest_model=model,
        transpose_before=True,
    )
    assert model.objects.objs == RESULT_


def test_mapping_array_1():
    data2 = [["A", 1, 4], ["B", 2, 5], ["C", 3, 6]]
    mapdict = ["X", "Y", "Z"]
    model = FakeDjangoModel()
    pe.save_as(
        array=data2,
        name_columns_by_row=0,
        dest_model=model,
        dest_mapdict=mapdict,
        transpose_before=True,
    )
    assert model.objects.objs == RESULT_


def test_mapping_dict_1():
    """

    for vertical sheet, first transpose it and then
    name columns by row 0
    """
    data2 = [["A", 1, 4], ["B", 2, 5], ["C", 3, 6]]
    mapdict = {"C": "Z", "A": "X", "B": "Y"}
    model = FakeDjangoModel()
    pe.save_as(
        array=data2,
        dest_model=model,
        dest_mapdict=mapdict,
        name_columns_by_row=0,
        transpose_before=True,
    )
    eq_(model.objects.objs, RESULT_)


def get_data2():
    return [["X", "Y", "Z"], [1, 2, 3], [4, 5, 6]]


RESULT3 = [{"Y": 2, "X": 1, "Z": 3}, {"Y": 5, "X": 4, "Z": 6}]


def test_sheet_save_to_django_model_2():
    model = FakeDjangoModel()
    sheet = pe.Sheet(get_data2(), name_columns_by_row=0)
    sheet.save_to_django_model(model)
    assert model.objects.objs == RESULT3


def test_sheet_save_to_django_model_3():
    model = FakeDjangoModel()
    sheet = pe.Sheet(get_data2())
    sheet.name_columns_by_row(0)

    def wrapper(row):
        row[0] = row[0] + 1
        return row

    sheet.save_to_django_model(model, initializer=wrapper)
    assert model.objects.objs == [
        {"Y": 2, "X": 2, "Z": 3},
        {"Y": 5, "X": 5, "Z": 6},
    ]


def test_model_save_to_django_model_3():
    model = FakeDjangoModel()
    pe.save_as(array=get_data2(), name_columns_by_row=0, dest_model=model)
    assert model.objects.objs == RESULT3


def test_model_save_to_django_model_2():
    model = FakeDjangoModel()
    pe.save_as(array=get_data2(), dest_model=model, name_columns_by_row=0)
    assert model.objects.objs == RESULT3


def test_load_sheet_from_django_model():
    model = FakeDjangoModel()
    sheet = pe.Sheet(get_data2(), name_columns_by_row=0)
    sheet.save_to_django_model(model)
    assert model.objects.objs == RESULT3
    model._meta.update(["X", "Y", "Z"])
    sheet2 = pe.get_sheet(model=model, sheet_name="test")
    sheet2.name_columns_by_row(0)
    assert sheet2.name == "test"
    eq_(list(sheet2.to_records()), list(sheet.to_records()))


def test_mapping_array():
    data2 = [["A", "B", "C"], [1, 2, 3], [4, 5, 6]]
    mapdict = ["X", "Y", "Z"]
    model = FakeDjangoModel()
    pe.save_as(
        array=data2,
        name_columns_by_row=0,
        dest_model=model,
        dest_mapdict=mapdict,
    )
    assert model.objects.objs == RESULT3


@raises(Exception)
def test_mapping_array_exceptional_case():
    data2 = [["A", "B", "C"], [1, 2, 3], [4, 5, 6]]
    mapdict = ["X", "Y", "Z"]
    model = FakeDjangoModel()
    pe.save_as(array=data2, dest_model=model, dest_mapdict=mapdict)
    assert model.objects.objs == RESULT3


def test_mapping_dict():
    data2 = [["A", "B", "C"], [1, 2, 3], [4, 5, 6]]
    mapdict = {"C": "Z", "A": "X", "B": "Y"}
    model = FakeDjangoModel()
    pe.save_as(
        array=data2,
        name_columns_by_row=0,
        dest_model=model,
        dest_mapdict=mapdict,
    )
    eq_(model.objects.objs, RESULT3)


def get_content():
    content = OrderedDict()
    content.update(
        {"Sheet1": [["X", "Y", "Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]]},
    )
    content.update(
        {"Sheet2": [["A", "B", "C"], [1, 4, 7], [2, 5, 8], [3, 6, 9]]},
    )
    return content


RESULT1 = [
    {"Y": 4, "X": 1, "Z": 7},
    {"Y": 5, "X": 2, "Z": 8},
    {"Y": 6, "X": 3, "Z": 9},
]

RESULT2 = [
    {"B": 4, "A": 1, "C": 7},
    {"B": 5, "A": 2, "C": 8},
    {"B": 6, "A": 3, "C": 9},
]


def test_book_save_to_models():
    model1 = FakeDjangoModel("Sheet1")
    model2 = FakeDjangoModel("Sheet2")
    book = pe.Book(get_content())
    book.save_to_django_models([model1, model2])
    assert model1.objects.objs == RESULT1
    assert model2.objects.objs == RESULT2


@raises(AttributeError)
def test_book_save_to_models_with_bulk_save_false():
    """
    same to previous test but with different parameters
    """
    model1 = FakeDjangoModel("Sheet1")
    model2 = FakeDjangoModel("Sheet2")
    book = pe.Book(get_content())
    book.save_to_django_models([model1, model2], bulk_save=False)


def test_model_save_to_models():
    model = FakeDjangoModel("Sheet1")
    data = {"Sheet1": [["X", "Y", "Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]]}
    pe.save_book_as(dest_models=[model, None, None], bookdict=data)
    assert model.objects.objs == RESULT1


def test_load_book_from_django_model():
    # if a book has more than one sheet
    # and it saves to only one model, now it will fail
    # with an exception.
    model = FakeDjangoModel("Sheet1")
    book = pe.Book(
        {"Sheet1": [["X", "Y", "Z"], [1, 4, 7], [2, 5, 8], [3, 6, 9]]},
    )
    book.save_to_django_models([model])
    assert model.objects.objs == RESULT1
    model._meta.update(["X", "Y", "Z"])
    book2 = pe.get_book(models=[model])
    assert book2[0].to_array() == book[0].to_array()


@raises(Exception)
def test_more_sheets_than_models():
    content = get_content()
    content = content.update({"IgnoreMe": [[1, 2, 3]]})
    model = FakeDjangoModel("Sheet1")
    pe.save_book_as(dest_models=[model], bookdict=content)
